﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ReconCaracteres_Backpropagation
{
    public class BackPropagationNetwork
    {
        public Boolean stopTrainningFlag;
        public Double alpha;
        public Layer inputLayer;
        public Layer hiddenLayer;
        public Layer outputLayer;
        public Double[] d;//expected values
        public Double[] errors;
        public Double[] factoresDeCambioSalida;
        public Double[] factoresDeCambioOculta;
        public BackPropagationNetwork(){
        
        }

        public BackPropagationNetwork(Double alpha,int inputLayerNeuronsAmount,int hiddenLayerNeuronsAmount, int outputLayerNeuronsAmount)
        {
            this.stopTrainningFlag = false;
            this.alpha = alpha;
            //int neuronsAmount = neuronsAmount;
            this.errors = new Double[outputLayerNeuronsAmount];
            this.d = new Double[outputLayerNeuronsAmount];
            this.factoresDeCambioSalida= new Double[outputLayerNeuronsAmount];
            this.factoresDeCambioOculta = new Double[hiddenLayerNeuronsAmount];
            //this.d = d;

            inputLayer = new Layer(null,inputLayerNeuronsAmount,hiddenLayerNeuronsAmount);

           // inputLayer.neurons[1].net = 0.5;
           // inputLayer.neurons[2].net = -0.5;



            hiddenLayer = new Layer(inputLayer, hiddenLayerNeuronsAmount, outputLayerNeuronsAmount);






            outputLayer = new Layer(hiddenLayer,outputLayerNeuronsAmount, 0);


        }
        public Double computeErros(){
            
            for (int i = 1; i < this.d.Count(); i++ )
            {
                this.errors[i] =  this.d[i] - this.outputLayer.neurons[i].net;
                if (Math.Abs(this.errors[i]) > 0.1)
                {
                    this.stopTrainningFlag = false;
                }
             
            }
            Double averageError = 0;
            for (int i = 1; i < this.errors.GetLength(0); i++ )
            {
                averageError += Math.Abs(errors[i]);
            }
            averageError = averageError / (this.errors.GetLength(0) - 1);
            return averageError;
        }

        public void computeFactoresDeCambioCapaOculta()
        {
            for (int i = 1; i < this.factoresDeCambioOculta.Count(); i++)
            {
                //this.factoresDeCambioSalida[i] = this.errors[i]*this.outputLayer.neurons[i].net*(1-this.outputLayer.neurons[i].net);
                Double primeraParte= this.hiddenLayer.neurons[i].net * (1.0 - this.hiddenLayer.neurons[i].net);
                Double segundaParte= this.factorDeCambioSalida(i);
                this.factoresDeCambioOculta[i] = primeraParte * segundaParte;
            }


        }
        public Double factorDeCambioSalida(int neuronIndex) {
            Double result = 0;
            for (int i = 1; i < this.factoresDeCambioSalida.Count(); i++)
            {
                result += this.factoresDeCambioSalida[i] * this.hiddenLayer.neurons[neuronIndex].weights[i - 1];
            }
            return result;
        }
        public void computeFactoresDeCambioCapaSalida()
        {
            for (int i = 1; i < this.factoresDeCambioSalida.Count(); i++)
            {
                this.factoresDeCambioSalida[i] = this.errors[i] * this.outputLayer.neurons[i].net * (1 - this.outputLayer.neurons[i].net);
            }
        }
        public void modifyOutputLayerWeights() {            
           this.hiddenLayer.modifyWeights(this.factoresDeCambioSalida, this.alpha);//in this implementation weights are linked to the hidden layer
        }
        public void modifyHiddenLayerWeights()
        {
            //factoresDeCambioOculta[0]=0;
            //factoresDeCambioOculta[1]=-0.01653;
            //factoresDeCambioOculta[2]=0.0018;
            this.inputLayer.modifyWeights(factoresDeCambioOculta, this.alpha);//in this implementation weights are linked to the hidden layer
        }
        public Double propagateInput(Double[] inputVector, Double[] d) {
            this.d = d;
            this.inputLayer.setInputs(inputVector);
            this.hiddenLayer.computeAllNets();
            this.outputLayer.computeAllNets();
            Double averageError=this.computeErros();
            this.computeFactoresDeCambioCapaSalida();
            this.computeFactoresDeCambioCapaOculta();
            this.modifyOutputLayerWeights();
            this.modifyHiddenLayerWeights();
            return averageError;
        }
        public Double[] clasifyInput(Double[] inputVector)
        {
            Double[] output=new Double[this.outputLayer.neurons.Count()-1];
            this.inputLayer.setInputs(inputVector);
            this.hiddenLayer.computeAllNets();
            this.outputLayer.computeAllNets();
            for (int i = 1; i < this.outputLayer.neurons.Count(); i++ )
            {
                output[i-1] = this.outputLayer.neurons[i ].net;
            }
            return output;
        }
    }
}
